#include "Shader.hpp"
#include "../../Renderer/ArcBallRenderer.hpp"
#include <Utility/FileTools.hpp>
#include <Utility/StringTools.hpp>
#include <Utility/Log.hpp>

namespace zzz{
//same reason as in texture, cannot initialize in constructor
Shader::Shader()
:vert(NULL),vert_hold(false),
frag(NULL),frag_hold(false),
geom(NULL),geohold_(false),
ProgramObject(0),is_linked(false)
{}

Shader::Shader(const char *vertfile, const char *fragfile, const char *geomfile) {
  if (ProgramObject==0) ProgramObject = glCreateProgram();
  if (vertfile!=NULL) LoadVertexFile(vertfile);
  if (fragfile!=NULL)  LoadFragmentFile(fragfile);
  if (geomfile!=NULL)  LoadGeometryFile(geomfile);
  CHECK_GL_ERROR()
}

Shader::Shader(GLVertexShader *verts, GLFragmentShader *frags, GLGeometryShader *geoms) {
  if (ProgramObject==0) ProgramObject = glCreateProgram();
  if (verts) AddShader(verts);
  if (frags) AddShader(frags);
  if (geoms) AddShader(geoms);
  CHECK_GL_ERROR()
}

Shader::~Shader() {
  if (vert) 
    glDetachShader(ProgramObject,vert->ShaderObject);
  if (vert_hold) 
    delete vert;
  if (frag) 
    glDetachShader(ProgramObject,frag->ShaderObject);
  if (frag_hold) 
    delete frag;
  if (geom) 
    glDetachShader(ProgramObject,geom->ShaderObject);
  if (geohold_) 
    delete geom;

  glDeleteShader(ProgramObject);
  CHECK_GL_ERROR()
}

bool Shader::AddShader(GLShaderObject* ShaderProgram) {
  if (ProgramObject==0) 
    ProgramObject = glCreateProgram();
  if (!ShaderProgram->is_compiled) 
    return false;
  if (ShaderProgram->progratype_==GLShaderObject::VERT) {
    vert=ShaderProgram;
    vert_hold=false;
  }
  else if (ShaderProgram->progratype_==GLShaderObject::FRAG) {
    frag=ShaderProgram;
    frag_hold=false;
  }
  else if (ShaderProgram->progratype_==GLShaderObject::GEOM) {
    geom=ShaderProgram;
    geohold_=false;
  }
  CHECK_GL_ERROR()
  return true;
}

bool Shader::LoadVertexFile(const char *file) {
  if (ProgramObject==0) 
    ProgramObject = glCreateProgram();
  if (!file) return true;
  vert=new GLVertexShader;
  bool ret=vert->LoadFromFile(file);
  vert_hold=true;
  CHECK_GL_ERROR()
  return ret;
}

bool Shader::LoadVertexMemory(const char *mem) {
  if (ProgramObject==0) 
    ProgramObject = glCreateProgram();
  if (!mem) return true;
  vert=new GLVertexShader;
  bool ret=vert->LoadFromMemory(mem);
  vert_hold=true;
  CHECK_GL_ERROR()
  return ret;
}

bool Shader::LoadFragmentFile(const char *file) {
  if (ProgramObject==0) 
    ProgramObject = glCreateProgram();
  if (!file) return true;
  frag=new GLFragmentShader;
  bool ret=frag->LoadFromFile(file);
  frag_hold=true;
  CHECK_GL_ERROR()
  return ret;
}

bool Shader::LoadFragmentMemory(const char *mem) {
  if (ProgramObject==0) 
    ProgramObject = glCreateProgram();
  if (!mem) return true;
  frag=new GLFragmentShader;
  bool ret=frag->LoadFromMemory(mem);
  frag_hold=true;
  CHECK_GL_ERROR()
  return ret;
}

bool Shader::LoadGeometryFile(const char *file) {
  if (ProgramObject==0) 
    ProgramObject = glCreateProgram();
  if (!file) return true;
  geom=new GLGeometryShader;
  bool ret=geom->LoadFromFile(file);
  geohold_=true;
  CHECK_GL_ERROR()
  return ret;
}

bool Shader::LoadGeometryMemory(const char *mem) {
  if (ProgramObject==0) 
    ProgramObject = glCreateProgram();
  if (!mem) return true;
  geom=new GLGeometryShader;
  bool ret=geom->LoadFromMemory(mem);
  geohold_=true;
  CHECK_GL_ERROR()
  return ret;
}

bool Shader::Link(void) {
  if (vert) glAttachShader(ProgramObject,vert->ShaderObject);
  if (frag) glAttachShader(ProgramObject,frag->ShaderObject);
  if (geom) glAttachShader(ProgramObject,geom->ShaderObject);

  int linked;
  glLinkProgram(ProgramObject);
  glGetProgramiv(ProgramObject, GL_LINK_STATUS, &linked);
  CHECK_GL_ERROR()

  if (linked) {
    is_linked = true;
    return true;
  } else {
    char *msg=GetLinkerError();
    ZLOGE << "Shader Linker ERROR:"<<msg<<endl;
    delete[] msg;
    return false;
  }
}

char* Shader::GetLinkerError(void) {
  int blen = 0;  
  int slen = 0;  
  glGetProgramiv(ProgramObject, GL_INFO_LOG_LENGTH , &blen);
  char *linker_log;
  if ((linker_log = (GLcharARB*)malloc(blen)) == NULL) {
    ZLOGE << "ERROR: Could not allocate compiler_log buffer\n";
    return NULL;
  }

  glGetProgramInfoLog(ProgramObject, blen, &slen, linker_log);
    
  CHECK_GL_ERROR()
  return (char*) linker_log;
}

void Shader::Begin(void) {
  if (ProgramObject == 0) return;
  if (is_linked) glUseProgram(ProgramObject);
  CHECK_GL_ERROR()
}

void Shader::End(void) {
  glUseProgram(0);
  CHECK_GL_ERROR()
}

bool Shader::SetUniform1f(char* varname, GLfloat v0) {
  GLint loc = GetUniLoc(varname);
  if (loc==-1) return false;  // can't find variable
  glUniform1f(loc, v0);
  CHECK_GL_ERROR()
  return true;
}

bool Shader::SetUniform2f(char* varname, GLfloat v0, GLfloat v1) {
  GLint loc = GetUniLoc(varname);
  if (loc==-1) return false;  // can't find variable
  glUniform2f(loc, v0, v1);
  CHECK_GL_ERROR()
  return true;
}

bool Shader::SetUniform3f(char* varname, GLfloat v0, GLfloat v1, GLfloat v2) {
  GLint loc = GetUniLoc(varname);
  if (loc==-1) return false;  // can't find variable
  glUniform3f(loc, v0, v1, v2);
  CHECK_GL_ERROR()
  return true;
}

bool Shader::SetUniform4f(char* varname, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3)
{
  GLint loc = GetUniLoc(varname);
  if (loc==-1) return false;  // can't find variable
  glUniform4f(loc, v0, v1, v2, v3);
  CHECK_GL_ERROR()
  return true;
}

bool Shader::SetUniform1i(char* varname, GLint v0) {
  GLint loc = GetUniLoc(varname);
  if (loc==-1) return false;  // can't find variable
  glUniform1i(loc, v0);
  CHECK_GL_ERROR()
  return true;
}

bool Shader::SetUniform2i(char* varname, GLint v0, GLint v1) {
  GLint loc = GetUniLoc(varname);
  if (loc==-1) return false;  // can't find variable
  glUniform2i(loc, v0, v1);
  CHECK_GL_ERROR()
  return true;
}

bool Shader::SetUniform3i(char* varname, GLint v0, GLint v1, GLint v2) {
  GLint loc = GetUniLoc(varname);
  if (loc==-1) return false;  // can't find variable
  glUniform3i(loc, v0, v1, v2);
  CHECK_GL_ERROR()
  return true;
}

bool Shader::SetUniform4i(char* varname, GLint v0, GLint v1, GLint v2, GLint v3) {
  GLint loc = GetUniLoc(varname);
  if (loc==-1) return false;  // can't find variable
  glUniform4i(loc, v0, v1, v2, v3);
  CHECK_GL_ERROR()
  return true;
}

bool Shader::SetUniform1fv(char* varname, GLsizei count, const GLfloat *value) {
  GLint loc = GetUniLoc(varname);
  if (loc==-1) return false;  // can't find variable
  glUniform1fv(loc, count, value);
  CHECK_GL_ERROR()
  return true;
}

bool Shader::SetUniform2fv(char* varname, GLsizei count, const GLfloat *value) {
  GLint loc = GetUniLoc(varname);
  if (loc==-1) return false;  // can't find variable
  glUniform2fv(loc, count, value);
  CHECK_GL_ERROR()
  return true;
}

bool Shader::SetUniform3fv(char* varname, GLsizei count, const GLfloat *value) {
  GLint loc = GetUniLoc(varname);
  if (loc==-1) return false;  // can't find variable
  glUniform3fv(loc, count, value);
  CHECK_GL_ERROR()
  return true;
}

bool Shader::SetUniform4fv(char* varname, GLsizei count, const GLfloat *value) {
  GLint loc = GetUniLoc(varname);
  if (loc==-1) return false;  // can't find variable
  glUniform4fv(loc, count, value);
  CHECK_GL_ERROR()
  return true;
}

bool Shader::SetUniform1iv(char* varname, GLsizei count, const GLint *value) {
  GLint loc = GetUniLoc(varname);
  if (loc==-1) return false;  // can't find variable
  glUniform1iv(loc, count, value);
  CHECK_GL_ERROR()
  return true;
}

bool Shader::SetUniform2iv(char* varname, GLsizei count, const GLint *value) {
  GLint loc = GetUniLoc(varname);
  if (loc==-1) return false;  // can't find variable
  glUniform2iv(loc, count, value);
  CHECK_GL_ERROR()
  return true;
}

bool Shader::SetUniform3iv(char* varname, GLsizei count, const GLint *value) {
  GLint loc = GetUniLoc(varname);
  if (loc==-1) return false;  // can't find variable
  glUniform3iv(loc, count, value);
  CHECK_GL_ERROR()
  return true;
}

bool Shader::SetUniform4iv(char* varname, GLsizei count, const GLint *value) {
  GLint loc = GetUniLoc(varname);
  if (loc==-1) return false;  // can't find variable
  glUniform4iv(loc, count, value);
  CHECK_GL_ERROR()
  return true;
}

bool Shader::SetUniformMatrix2fv(char* varname, GLsizei count, GLboolean transpose, const GLfloat *value) {
  GLint loc = GetUniLoc(varname);
  if (loc==-1) return false;  // can't find variable
  glUniformMatrix2fv(loc, count, transpose, value);
  CHECK_GL_ERROR()
  return true;
}

bool Shader::SetUniformMatrix3fv(char* varname, GLsizei count, GLboolean transpose, const GLfloat *value) {
  GLint loc = GetUniLoc(varname);
  if (loc==-1) return false;  // can't find variable
  glUniformMatrix3fv(loc, count, transpose, value);
  CHECK_GL_ERROR()
  return true;
}

bool Shader::SetUniformMatrix4fv(char* varname, GLsizei count, GLboolean transpose, const GLfloat *value) {
  GLint loc = GetUniLoc(varname);
  if (loc==-1) return false;  // can't find variable
  glUniformMatrix4fv(loc, count, transpose, value);
  CHECK_GL_ERROR()
  return true;
}

GLint Shader::GetUniLoc(const GLchar *name) {
  GLint loc;
  loc = glGetUniformLocation(ProgramObject, name);
  if (loc == -1) ZLOGE << "Error: can't find uniform variable \"" << name << "\"\n";
  CHECK_GL_ERROR()
  return loc;
}

GLint Shader::GetAttLoc(const GLchar *name) {
  GLint loc;
  loc = glGetAttribLocation(ProgramObject, name);
  if (loc == -1) ZLOGE << "Error: can't find attribute variable \"" << name << "\"\n";
  CHECK_GL_ERROR()
  return loc;
}

void Shader::GetUniformfv(char* name, GLfloat* values)
{
  GLint loc;
  loc = glGetUniformLocation(ProgramObject, name);
  if (loc == -1) ZLOGE << "Error: can't find uniform variable \"" << name << "\"\n";
  else glGetUniformfv(ProgramObject, loc, values);
  CHECK_GL_ERROR()
}

void Shader::GetUniformiv(char* name, GLint* values)
{
  GLint loc;
  loc = glGetUniformLocation(ProgramObject, name);
  if (loc == -1) ZLOGE << "Error: can't find uniform variable \"" << name << "\"\n";
  else glGetUniformiv(ProgramObject, loc, values);
  CHECK_GL_ERROR()
}

bool Shader::SetVertexAttrib1s(GLuint index, GLshort v0) {
  glVertexAttrib1s(index, v0);
  CHECK_GL_ERROR()
  return true;
}

bool Shader::SetVertexAttrib2s(GLuint index, GLshort v0, GLshort v1) {
  glVertexAttrib2s(index, v0, v1);
  CHECK_GL_ERROR()
  return true;
}

bool Shader::SetVertexAttrib3s(GLuint index, GLshort v0, GLshort v1, GLshort v2) {
  glVertexAttrib3s(index, v0, v1, v2);
  CHECK_GL_ERROR()
  return true;
}

bool Shader::SetVertexAttrib4s(GLuint index, GLshort v0, GLshort v1, GLshort v2, GLshort v3) {
  glVertexAttrib4s(index, v0, v1, v2, v3);
  CHECK_GL_ERROR()
  return true;
}

bool Shader::SetVertexAttrib1f(GLuint index, GLfloat v0) {
  glVertexAttrib1f(index, v0);
  CHECK_GL_ERROR()
  return true;
}

bool Shader::SetVertexAttrib2f(GLuint index, GLfloat v0, GLfloat v1) {
  glVertexAttrib2f(index, v0, v1);
  CHECK_GL_ERROR()
  return true;
}

bool Shader::SetVertexAttrib3f(GLuint index, GLfloat v0, GLfloat v1, GLfloat v2) {
  glVertexAttrib3f(index, v0, v1, v2);
  CHECK_GL_ERROR()
  return true;
}

bool Shader::SetVertexAttrib4f(GLuint index, GLfloat v0, GLfloat v1, GLfloat v2, GLfloat v3) {
  glVertexAttrib4f(index, v0, v1, v2, v3);
  CHECK_GL_ERROR()
  return true;
}

bool Shader::SetVertexAttrib1d(GLuint index, GLdouble v0) {
  glVertexAttrib1d(index, v0);
  CHECK_GL_ERROR()
  return true;
}

bool Shader::SetVertexAttrib2d(GLuint index, GLdouble v0, GLdouble v1) {
  glVertexAttrib2d(index, v0, v1);
  CHECK_GL_ERROR()
  return true;
}

bool Shader::SetVertexAttrib3d(GLuint index, GLdouble v0, GLdouble v1, GLdouble v2) {
  glVertexAttrib3d(index, v0, v1, v2);
  CHECK_GL_ERROR()
  return true;
}

bool Shader::SetVertexAttrib4d(GLuint index, GLdouble v0, GLdouble v1, GLdouble v2, GLdouble v3) {
  glVertexAttrib4d(index, v0, v1, v2, v3);
  CHECK_GL_ERROR()
  return true;
}

bool Shader::SetUniforms(Renderer *currenderer)
{
  if (ProgramObject == 0) return false;
  if (!is_linked) return false;

  for (zuint i=0; i<UniformParamList.size(); i++) {
    UniformParam *tmp=&UniformParamList[i];
    char varname[100];
    strcpy(varname,tmp->name.c_str());
    if (tmp->type==UniformParam::F1)
      SetUniform1f(varname,tmp->f1);
    else if (tmp->type==UniformParam::F2)
      SetUniform2f(varname,tmp->f2[0],tmp->f2[1]);
    else if (tmp->type==UniformParam::F3)
      SetUniform3f(varname,tmp->f3[0],tmp->f3[1],tmp->f3[2]);
    else if (tmp->type==UniformParam::F4)
      SetUniform4f(varname,tmp->f4[0],tmp->f4[1],tmp->f4[2],tmp->f4[3]);
    else if (tmp->type==UniformParam::I1)
      SetUniform1i(varname,tmp->i1);
    else if (tmp->type==UniformParam::I2)
      SetUniform2i(varname,tmp->i2[0],tmp->i2[1]);
    else if (tmp->type==UniformParam::I3)
      SetUniform3i(varname,tmp->i3[0],tmp->i3[1],tmp->i3[2]);
    else if (tmp->type==UniformParam::I4)
      SetUniform4i(varname,tmp->i4[0],tmp->i4[1],tmp->i4[2],tmp->i4[3]);
    else if (tmp->type==UniformParam::M2)
      SetUniformMatrix2fv(varname, 1, GL_FALSE, tmp->m2);
    else if (tmp->type==UniformParam::M3)
      SetUniformMatrix3fv(varname, 1, GL_FALSE, tmp->m3);
    else if (tmp->type==UniformParam::M4)
      SetUniformMatrix4fv(varname, 1, GL_FALSE, tmp->m4);
    else if (currenderer==NULL)
    {
      printf("ERROR: SetUniforms: no Renderer is specified\n");
      continue;
    }
//     else if (tmp->type==UniformParam::OBJROT) 
//     {
//       ArcBallRenderer *renderer=dynamic_cast<ArcBallRenderer*>(currenderer);
//       SetUniformMatrix4fv(varname,1,0,renderer->pCurObjArcBall_->GetGLRotateMatrix());
//     }
//     else if (tmp->type==UniformParam::OBJROTTRANS)
//     {
//       ArcBallRenderer *renderer=dynamic_cast<ArcBallRenderer*>(currenderer);
//       SetUniformMatrix4fv(varname,1,1,renderer->pCurObjArcBall_->GetGLRotateMatrix());
//     }
//     else if (tmp->type==UniformParam::ENVROT)
//     {
//       ArcBallRenderer *renderer=dynamic_cast<ArcBallRenderer*>(currenderer);
//       SetUniformMatrix4fv(varname,1,0,renderer->pCurEnvArcBall_->GetGLRotateMatrix());
//     }
//     else if (tmp->type==UniformParam::ENVROTTRANS)
//     {
//       ArcBallRenderer *renderer=dynamic_cast<ArcBallRenderer*>(currenderer);
//       SetUniformMatrix4fv(varname,1,1,renderer->pCurEnvArcBall_->GetGLRotateMatrix());
//     }
  }
  CHECK_GL_ERROR()
  return true;
}

bool Shader::ChangeUniform(const string &name,GLint i1)
{
  vector<UniformParam>::iterator vupi=find(UniformParamList.begin(),UniformParamList.end(),name);
  if (vupi==UniformParamList.end()) return false;
  vupi->i1=i1;
  CHECK_GL_ERROR()
  return true;
}

bool Shader::ChangeUniform(const string &name,GLfloat f1)
{
  vector<UniformParam>::iterator vupi=find(UniformParamList.begin(),UniformParamList.end(),name);
  if (vupi==UniformParamList.end()) return false;
  vupi->f1=f1;
  CHECK_GL_ERROR()
  return true;
}

bool Shader::Clear()
{
  UniformParamList.clear();
  CHECK_GL_ERROR()
  return true;
}

//////////////////////////////////////////////////////////////////////////

GLShaderObject::GLShaderObject(ShaderType type)
:progratype_(type),is_compiled(false),ShaderObject(0)
{}

GLShaderObject::~GLShaderObject()
{
  if (is_compiled)
    glDeleteObjectARB(ShaderObject);
  CHECK_GL_ERROR()
}

bool GLShaderObject::LoadFromFile(const char * filename)
{
  char *source;
  if (!ReadFileToString(filename,&source)) return false;
  bool ret=Compile(source);
  delete source;
  CHECK_GL_ERROR()
  return ret;
}

bool GLShaderObject::LoadFromMemory(const char * program)
{
  char *source = (char*)program;
  return Compile(source);
}

char* GLShaderObject::GetCompilerError(void)
{
  int blen = 0;
  int slen = 0;
  glGetShaderiv(ShaderObject, GL_INFO_LOG_LENGTH , &blen);
  char *compiler_log;
  if ((compiler_log = (GLcharARB*)malloc(blen)) == NULL)
  {
    ZLOGE<< "ERROR: Could not allocate compiler_log buffer\n";
    return NULL;
  }
  glGetInfoLogARB(ShaderObject, blen, &slen, compiler_log);
  CHECK_GL_ERROR()
  return compiler_log;
}

bool GLShaderObject::Compile(char *source)
{
  is_compiled = false;
  int compiled = 0;

  GLint length = (GLint) strlen((const char *)source);
  glShaderSource(ShaderObject, 1, (const GLcharARB **)&source, &length);
  glCompileShader(ShaderObject);
  glGetObjectParameterivARB(ShaderObject, GL_COMPILE_STATUS, &compiled);
  CHECK_GL_ERROR()
  
  if (compiled) 
  {
    is_compiled=true;
    return true;
  }
  else
  {
    char *msg=GetCompilerError();
    if (progratype_==VERT) ZLOGE << "Vertex Shader Compiler ERROR:"<<msg<<endl;
    else if (progratype_==FRAG) ZLOGE << "Fragment Shader Compiler ERROR:"<<msg<<endl;
    delete[] msg;
    return false;
  }
}

GLint GLShaderObject::GetAttribLocation(char* attribName)
{
  return glGetAttribLocationARB(ShaderObject, attribName);
}

//////////////////////////////////////////////////////////////////////////

GLVertexShader::GLVertexShader()
:GLShaderObject(VERT)
{
  ShaderObject = glCreateShaderObjectARB(GL_VERTEX_SHADER);
  CHECK_GL_ERROR()
}

//////////////////////////////////////////////////////////////////////////

GLFragmentShader::GLFragmentShader()
:GLShaderObject(FRAG)
{
  ShaderObject = glCreateShaderObjectARB(GL_FRAGMENT_SHADER);
  CHECK_GL_ERROR()
}

//////////////////////////////////////////////////////////////////////////

GLGeometryShader::GLGeometryShader()
:GLShaderObject(GEOM)
{
  ShaderObject = glCreateShaderObjectARB(GL_GEOMETRY_SHADER);
  CHECK_GL_ERROR()
}
}
